package cn.mw.cmdb.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.mw.cmdb.param.ModelPropertyParam;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class JsonComparatorUtil {
    private static final ObjectMapper mapper = new ObjectMapper();

    public static final String SPAN_LEFT = "<span style='color: #faad14;'>";
    public static final String SPAN_RIGHT_ADD_BR = "</span><br/>";
    public static final String SPAN_RIGHT = "</span>";

    public static final String EQ = "=";

    public static void main(String[] args) throws IOException {


        String jsonBefore = "{\"internal\": false, \"data\": {\"f8e37381-b7d6-4475-95d3-e763b64306e8\": \"33\", \"mw_instanceName\": \"kkkkkk\", \"flag\": true}, \"instanceName\": \"kkkkkk\", \"modelId\": \"104730838641410048\", \"groupId\": \"102706472617508864\", \"templateSearchParam\": {\"isExportTemp\": false}, \"type\": \"instance\", \"path\": [\"77170433597636608\", \"102706472617508864\", \"104730838641410048\"], \"itemName\": \"kkkkkk\", \"orgIds\": [1]}";
        String jsonAfter = "{\"internal\": true, \"data\": {\"f8e37381-b7d6-4475-95d3-e763b64306e8\": \"33\", \"mw_instanceName\": \"llllll\", \"flag\": false}, \"instanceName\": \"kkkkkk\", \"modelId\": \"104730838641410048\", \"groupId\": \"102706472617508864\", \"templateSearchParam\": {\"isExportTemp\": false}, \"type\": \"instance\", \"path\": [\"77170433597636608\", \"102706472617508864\", \"104730838641410048\"], \"itemName\": \"kkkkkk\", \"orgIds\": [1]}";
        List<String> fieldsToMonitor = Arrays.asList("internal", "instanceName", "mw_instanceName");

        JsonNode jsonBeforeNode = mapper.readTree(jsonBefore);
        JsonNode jsonAfterNode = mapper.readTree(jsonAfter);

        DiffResult diffResult = generateHtmlWithHighlights(jsonBeforeNode, jsonAfterNode);

        System.out.println("HTML for json1Before: " + diffResult.getBefore());
        System.out.println("HTML for jsonAfter: " + diffResult.getAfter());
    }

    public static DiffResult generateHtmlWithHighlights(JsonNode jsonBeforeNode, JsonNode jsonAfterNode,
                                                        List<String> fieldsToMonitor, Map<String, ModelPropertyParam> propertyMap) throws JsonProcessingException {
        DiffResult diffResult = new DiffResult();
        List<String> changeSummaryList = new ArrayList<>();
        // 获取差异并进行高亮处理
        Map<String, String> differences = getDifferences(jsonBeforeNode, jsonAfterNode);
        List<String> propertyFieldList = new ArrayList<>();
        // key: 源字段  value:属性中获取的对应的字段(基本为中文)
        Map<String, String> propertyMappingFieldMap = new HashMap<>();
        fieldsToMonitor.forEach(item -> {
            ModelPropertyParam propertyParam = propertyMap.get(item);
            if (ObjUtil.isNotEmpty(propertyParam)) {
                String propertyName = propertyParam.getName();
                propertyFieldList.add(propertyName);
                propertyMappingFieldMap.put(item, propertyName);
            }
        });
        log.info("传入的字段名与转换后的字段名: {}", propertyMappingFieldMap);
        if (!changeFieldInclusion(propertyFieldList, differences.keySet())) {
            return null;
        }
        for (Map.Entry<String, String> entry : differences.entrySet()) {
            if (CollUtil.isNotEmpty(fieldsToMonitor)) {
                if (propertyFieldList.contains(entry.getKey())) {
                    changeSummaryList.add(entry.getKey() + EQ + entry.getValue());
                }
            } else {
                changeSummaryList.add(entry.getKey() + EQ + entry.getValue());
            }
        }
        if (CollUtil.isNotEmpty(changeSummaryList)) {
            diffResult.setChangeSummary(String.join(";", changeSummaryList));
        }
        if (ObjUtil.isNotEmpty(jsonBeforeNode)) {
            String jsonBefore = highlightDifference(jsonBeforeNode, jsonAfterNode, propertyFieldList);
            diffResult.setBefore(jsonBefore);
        }
        if (ObjUtil.isNotEmpty(jsonAfterNode)) {
            String jsonAfter = highlightDifference(jsonAfterNode, jsonBeforeNode, propertyFieldList);
            diffResult.setAfter(jsonAfter);
        }
        return diffResult;
    }

    private static boolean changeFieldInclusion(List<String> propertyFieldList, Set<String> allFields) {
        Set<String> propertyFieldSet = propertyFieldList.stream().map(String::toLowerCase).collect(Collectors.toSet());
        for (String field : allFields) {
            if (propertyFieldSet.contains(field.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    private static String highlightDifference(JsonNode node1, JsonNode node2, List<String> fieldsToMonitor) {
        StringBuilder html = new StringBuilder();
        if (node1.isObject() && node2.isObject()) {
            ObjectNode objectNode1 = (ObjectNode) node1;
            ObjectNode objectNode2 = (ObjectNode) node2;
            for (String field : fieldsToMonitor) {
                JsonNode fieldNode1 = findField(objectNode1, field);
                JsonNode fieldNode2 = findField(objectNode2, field);
                if (fieldNode1 != null && fieldNode2 != null) {
                    if (!fieldNode1.equals(fieldNode2)) {
                        html.append(field).append(": ").append(SPAN_LEFT).append(fieldNode1.asText()).append(SPAN_RIGHT_ADD_BR);
                    }
                } else if (fieldNode1 != null) {
                    html.append(field).append(": ").append(SPAN_LEFT).append(fieldNode1.asText()).append(SPAN_RIGHT_ADD_BR);
                } else if (fieldNode2 != null) {
                    html.append(field).append(": ").append(SPAN_LEFT).append(fieldNode2.asText()).append(SPAN_RIGHT_ADD_BR);
                }
            }
        }
        return html.toString();
    }

    private static JsonNode findField(ObjectNode node, String fieldName) {
        if (node.has(fieldName)) {
            return node.get(fieldName);
        }
        for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
            String key = it.next();
            JsonNode childNode = node.get(key);
            if (childNode.isObject()) {
                JsonNode result = findField((ObjectNode) childNode, fieldName);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }


    public static DiffResult generateHtmlWithHighlights(JsonNode node1, JsonNode node2) {
        try {
            DiffResult diffResult = new DiffResult();

            List<String> changeSummaryList = new ArrayList<>();
            // 获取差异并进行高亮处理
            Map<String, String> differences = getDifferences(node1, node2);
            for (Map.Entry<String, String> entry : differences.entrySet()) {
                changeSummaryList.add(entry.getKey() + EQ + entry.getValue());
            }
            if (CollUtil.isNotEmpty(changeSummaryList)) {
                diffResult.setChangeSummary(String.join(";", changeSummaryList));
            }
            if (ObjUtil.isNotEmpty(node1)) {
                ObjectNode highlightedNode = mapper.createObjectNode();
                highlightDifference(node1, differences, highlightedNode, "");
                // 将差异节点转换为字符串
                String beforeStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(highlightedNode);
                diffResult.setBefore(beforeStr);
            }
            if (ObjUtil.isNotEmpty(node2)) {
                ObjectNode highlightedNode = mapper.createObjectNode();
                highlightDifference(node2, differences, highlightedNode, "");
                // 将差异节点转换为字符串
                String afterStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(highlightedNode);
                diffResult.setAfter(afterStr);
            }
            // 创建 DiffResult 对象并返回
            return diffResult;
        } catch (Exception e) {
            log.error("generateHtmlWithHighlights 异常: ", e);
            return null;
        }
    }

    /**
     * 获取两个 JSON 对象的差异
     *
     * @param node1 before 节点
     * @param node2 after 节点
     * @return
     */
    private static Map<String, String> getDifferences(JsonNode node1, JsonNode node2) throws JsonProcessingException {
        Map<String, String> differences = new LinkedHashMap<>();
        compareJsonNodes(node1, node2, differences);
        return differences;
    }

    /**
     * 递归比较两个 JSON 节点的差异
     *
     * @param node1       before 节点
     * @param node2       after 节点
     * @param differences 差异字段
     */
    private static void compareJsonNodes(JsonNode node1, JsonNode node2, Map<String, String> differences) throws JsonProcessingException {
        Iterator<String> fieldNames = node1.fieldNames();
        Set<String> nodeFields = new HashSet<>();
        while (fieldNames.hasNext()) {
            nodeFields.add(fieldNames.next());
        }
        Iterator<String> fielded = node2.fieldNames();
        while (fielded.hasNext()) {
            nodeFields.add(fielded.next());
        }
        for (String fieldName : nodeFields) {
            JsonNode fieldValue1 = Optional.ofNullable(node1.get(fieldName)).orElse(mapper.readTree(StrUtil.EMPTY_JSON));
            JsonNode fieldValue2 = Optional.ofNullable(node2.get(fieldName)).orElse(mapper.readTree(StrUtil.EMPTY_JSON));

            // 如果是对象类型，则继续比较
            if (fieldValue1.isObject() && fieldValue2.isObject()) {
                compareJsonNodes(fieldValue1, fieldValue2, differences);
                // 跳过本次后续操作
                continue;
            }

            if (!Objects.equals(fieldValue1, fieldValue2)) {
                if (null == fieldValue2) {
                    differences.put(fieldName, "");
                } else {
                    differences.put(fieldName, fieldValue2.toString());
                }
            }
        }

    }

    private static void highlightDifference(JsonNode node, Map<String, String> differences,
                                            ObjectNode highlightedNode, String key) {
        try {
            Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
            ObjectNode objectNode = null;
            while (fields.hasNext()) {
                Map.Entry<String, JsonNode> field = fields.next();
                String fieldName = field.getKey();
                JsonNode fieldValue = field.getValue();

                if (fieldValue.isObject()) {
                    // 如果是对象，递归查看内部属性；
                    highlightDifference(fieldValue, differences, highlightedNode, fieldName);
                }
                if (StrUtil.isNotEmpty(key)) {
                    if (ObjUtil.isEmpty(objectNode)) {
                        // 初始化 objectNode
                        objectNode = new ObjectMapper().createObjectNode();
                    }
                    // 差异化包含 fieldName 设置html元素
                    if (differences.containsKey(fieldName)) {
                        objectNode.put(fieldName,
                                SPAN_LEFT + fieldValue + SPAN_RIGHT_ADD_BR);
                    } else {
                        // 差异化不包含正常添加数据
                        objectNode.set(fieldName, fieldValue);
                    }
                } else {
                    if (differences.containsKey(fieldName)) {
                        highlightedNode.put(fieldName,
                                SPAN_LEFT + fieldValue + SPAN_RIGHT_ADD_BR);
                    } else {
                        // 避免原生对象数据后期替换掉之前设置的 objectNode
                        if (highlightedNode.get(fieldName) == null) {
                            highlightedNode.set(fieldName, fieldValue);
                        }
                    }
                }
            }
            if (ObjUtil.isNotEmpty(objectNode) && StrUtil.isNotEmpty(key)) {
                highlightedNode.set(key, objectNode);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Data
    public static class DiffResult {
        private String before;
        private String after;
        private String changeSummary;
    }
}
